在昨天的介紹的客製化網路路由一文提到,當網站伺服器收到請求,將此請求傳遞給 WordPress 解析完網址之後,會將生成 SQL 查詢語句,向 MySQL 資料庫查詢,接著取得資料後並生成內容輸出到網頁上。

圖:生成內容流程圖
而今天要介紹的主角就是 WP_Query 類別,它 WordPress 核心中,負責生成 SQL 查詢語句的組件。

圖:WP_Query 實例化過程
它的實例化是在 wp-settings.php 檔案中,大約 516 行位置,並將此實例放在全域變數 $wp_the_query 和 $wp_query 中。
以主要鉤點的生命週期來說,位於 sanitize_comment_cookies 和 setup_theme 之間。因此我們除了可以在佈景主題中直接使用 $wp_query 這個全域變數,在 setup_theme 之後的鉤點也可以使用。
適合使用 WP_Query 的情境有兩種。
要找出 WordPress目前正在處理的是哪一種請求,非常簡單,因為 WP_Query 類別中的 $is_ 屬性就是設計來存放這個資訊的,並在這裡使用條件標籤來互動。對外掛的開發者來說非常有用。
以下是部分屬性的例子:
$is_single:如果當前請求是查看單一文章頁面,此屬性為 true。$is_page:如果當前是一個靜態頁面的請求,此屬性為 true。$is_archive:如果當前請求是一個彙整頁面,例如分類、標籤、作者等存檔頁面,此屬性為 true。條件標籤指的是 WordPress 提供的一套函式,這些函數可以用來檢查當前請求的各種條件,並基於這些條件執行不同的程式邏輯。條件標籤和  WP_Query 實例的 $is_ 屬性直接相關,並提供了一個更簡潔、更方便的方式來進行檢查。
以下是部分條件標籤函式的例子:
is_single():檢查當前是否為單一文章頁面。is_page():檢查當前是否為一個靜態頁面。is_archive():檢查當前是否為一個彙整頁面。The Loop 用在佈景主題的範本檔案中,WP_Query 提供了許多的方法來完成 The Loop 內的常見功能,例如:
if ( $the_query->have_posts() ) {
    while ( $the_query->have_posts() ) {
        $the_query->the_post();
        // 在這之後使用內容輸出的相關函式
    }
}
先使用 have_posts 方法來判斷是否有文章資料,如果有,就開始一個 while 迴圈,同樣使用 have_posts 作為條件,當所有文章在迴圈中被處理過(即指針移動到了最後一個文章之後的位置), have_posts 會返回 false,結束 while 迴圈。
在每次迴圈中,呼叫 the_post 方法,它會設定全域變數 $post 讓其它依賴這個全域變數的函式可以使用。
建構子的參位為陣列,根據查詢種類的不同有不一樣的條件,按此查詢。
| 名稱 | 說明 | 
|---|---|
| $query | 傳遞給 $wp_query物件的查詢字符串。 | 
| $query_vars | 包含解析的 $query的關聯陣列,查詢變數及其相對應的值的陣列。 | 
| $queried_object | 如果請求是分類、作者或頁面,此屬性保存其相關資訊。 | 
| $queried_object_id | 如果請求是分類、作者或頁面,此屬性保存相應的 ID。 | 
| $posts | 從資料庫取得的文章。 | 
| $post_count | 正在顯示的文章數量。 | 
| $found_posts | 符合當前查詢參數的文章總數。 | 
| $max_num_pages | 總頁數。 是 $found_posts / $posts_per_page的結果。 | 
| $current_post | (在 The Loop 期間可用) 目前正在顯示的文章的索引。 | 
| $post | (在 The Loop 期間可用) 目前正在顯示的文章。 | 
可直接使用公開的屬性,例如 $queries = $wp_query->query_vars。
| 名稱 | 說明 | 
|---|---|
| $is_single | 是否是單一文章的請求。 | 
| $is_page | 是否是頁面的請求。 | 
| $is_archive | 是否是任何類型的存檔頁面的請求。 | 
| $is_preview | 是否是文章預覽的請求。 | 
| $is_date | 是否是日期存檔的請求。 | 
| $is_year | 是否是年存檔的請求。 | 
| $is_month | 是否是月存檔的請求。 | 
| $is_time | 是否是時間存檔的請求。 | 
| $is_author | 是否是作者存檔的請求。 | 
| $is_category | 是否是分類存檔的請求。 | 
| $is_tag | 是否是標籤存檔的請求。 | 
| $is_tax | 是否是自訂分類法存檔的請求。 | 
| $is_search | 是否是搜索結果的請求。 | 
| $is_feed | 是否是訂閱源的請求。 | 
| $is_comment_feed | 是否是評論訂閱源的請求。 | 
| $is_trackback | 是否是跟踪回溯的請求。 | 
| $is_home | 是否是首頁的請求。 | 
| $is_404 | 是否是404錯誤頁面的請求。 | 
| $is_comments_popup | 是否是評論彈出窗口的請求。 | 
| $is_admin | 是否是管理區的請求。 | 
| $is_attachment | 是否是附件頁面的請求。 | 
| $is_singular | 是否是單一文章、頁面或附件的請求。 | 
| $is_robots | 是否是針對 robots.txt檔案的請求。 | 
| $is_posts_page | 是否是文章列表頁面的請求。 | 
| $is_paged | 是否是分頁的請求。 | 
狀態類的屬性對應了狀態標籤函式,例如 $is_single 對應到  is_single()。
| 連結 | 說明 | 
|---|---|
| fill_query_vars | 填充不存在於參數內的查詢變數。 | 
| generate_cache_key | 產生快取鍵。 | 
| generate_postdata | 產生文章資料。 | 
| get | 檢索查詢變數的值。 | 
| get_posts | 根據查詢變數檢索一系列的文章。 | 
| get_queried_object | 檢索當前查詢的對象。 | 
| get_queried_object_id | 檢索當前查詢對象的 ID。 | 
| get_search_stopwords | 檢索解析搜尋詞語時使用的停用詞。 | 
| have_comments | 判斷是否還有更多的迴響可用。 | 
| have_posts | 判斷在迴圈中是否還有更多的文章可用。 | 
| init | 初始化物件屬性並設定預設值。 | 
| init_query_flags | 重置查詢標誌為 false。 | 
| next_comment | 迭代當前迴響索引並返回 WP_Comment對象。 | 
| next_post | 設定下一篇文章並迭代當前文章索引。 | 
| parse_order | 解析 order查詢變量並根據需要將其轉換為ASC或DESC。 | 
| parse_orderby | 將給定的 orderby別名(如果允許的話)轉換為正確前綴的值。 | 
| parse_query | 解析查詢字串並設定查詢類型布林值。 | 
| parse_query_vars | 重新解析查詢變量。 | 
| parse_search | 根據傳遞的搜尋字詞生成 WHERE子句的 SQL。 | 
| parse_search_order | 根據傳遞的搜尋全域生成 ORDER BY條件的 SQL。 | 
| parse_search_terms | 檢查字詞是否適合搜尋。 | 
| parse_tax_query | 解析各種與分類相關的查詢變量。 | 
| query | 通過解析查詢字串來設定 WordPress 查詢。 | 
| reset_postdata | 在迴圈過一個嵌套查詢後,此函數將 $post全域變數恢復為此查詢中的當前文章。 | 
| rewind_comments | 倒回迴響,重置迴響索引和迴響到第一個指針。 | 
| rewind_posts | 倒回文章並重置文章索引指針。 | 
| set | 設定查詢變數的值。 | 
| set_404 | 設置404 屬性並保存查詢是否為 RSS Feed。 | 
| set_found_posts | 為當前查詢設置找到的文章數量和頁面數(如果使用了限制子句)。 | 
| setup_postdata | 設置全域文章資料。 | 
| the_comment | 設置當前迴響。 | 
| the_post | 設置當前文章。 | 
| 連結 | 說明 | 
|---|---|
| is_404 | 判斷查詢是否為404(無結果)。 | 
| is_archive | 判斷查詢是否為現有的彙整頁面。 | 
| is_attachment | 判斷查詢是否為現有的附件頁面。 | 
| is_author | 判斷查詢是否為現有的作者彙整頁面。 | 
| is_category | 判斷查詢是否為現有的分類彙整頁面。 | 
| is_comment_feed | 判斷查詢是否為迴響訂閱。 | 
| is_date | 判斷查詢是否為現有的日期彙整。 | 
| is_day | 判斷查詢是否為現有的日彙整。 | 
| is_embed | 判斷查詢是否為嵌入的文章。 | 
| is_favicon | 判斷查詢是否為 favicon.ico檔案。 | 
| is_feed | 判斷查詢是否為訂閱。 | 
| is_front_page | 判斷查詢是否為網站的首頁。 | 
| is_home | 判斷查詢是否為部落格首頁。 | 
| is_main_query | 判斷查詢是否為主查詢。 | 
| is_month | 判斷查詢是否為現有的月份彙整。 | 
| is_page | 判斷查詢是否為現有的單一頁面。 | 
| is_paged | 判斷查詢是否為分頁結果且不是第一頁。 | 
| is_post_type_archive | 判斷查詢是否為現有的文章類型彙整頁面。 | 
| is_preview | 判斷查詢是否為文章或頁面預覽。 | 
| is_privacy_policy | 判斷查詢是否為隱私政策頁面。 | 
| is_robots | 判斷查詢是否為 robots.txt檔案。 | 
| is_search | 判斷查詢是否為搜尋。 | 
| is_single | 判斷查詢是否為現有的單一文章。 | 
| is_singular | 判斷查詢是否為現有的任何文章類型的單一文章(文章、附件、頁面、自定文章類型)。 | 
| is_tag | 判斷查詢是否為現有的標籤彙整頁面。 | 
| is_tax | 判斷查詢是否為現有的自定義分類彙整頁面。 | 
| is_time | 判斷查詢是否為特定時間。 | 
| is_trackback | 判斷查詢是否為 trackback 端點呼叫。 | 
| is_year | 判斷查詢是否為現有的年份彙整。 | 
WP_Query 類別中的這些方法又被包裝為全域的函式方便呼叫。
$args = array(
    'post_type' => 'post',
    'posts_per_page' => 5
);
$the_query = new WP_Query( $args );
這段簡短的程式碼,正是 WP_Query 類別的基本應用。$args 為查詢的條件。以這個例子來說,文章類型為 post,每頁筆數為 5。以此條件實例化後的 $the_query 即可在佈景主題中使用。

圖:鐵人賽相關文章程式範例
比較複雜一點的應用,例如要在文章頁面底下加上一個「鐵人賽相關文章」區塊,程式碼大概如上圖所示。
第 1 行:狀態標簽函式,判斷是否為單一文章頁面。
第 4 行至第 7 行:給 WP_Query 的查詢參數,找的是文章類型,顯示 5 筆。
第 9 行:實例化 WP_Query 類別。
第 14 行:更新文章所需的全域變數。
第 11 行至第 22 行:執行 The Loop,印出帶有文章連結的文章標題。
WP_Query 類別主要用在佈景主題範本中,用於生成 SQL 查詢語句,並提供我們專門處理文章迴圈的方法,比較要注意的是 The Loop 的概念比較不直覺,它是採用在迴圈的區域內依次更新全域變數,接著依賴這些全域變數的函式才能正確輸出當前在迴圈中的文章,一開始如果不大習慣這樣的邏輯,多用幾次就習慣了。
課後思考:
WP_Query 類別提供了豐富的參數來查詢對應的資料表,但有些參數可能會產生不佳的 SQL 語句查詢,產生效能的問題,該如何避免這種情況發生?
前篇解答參考:
當發現 MySQL 的 binlog 長大的速度異常,或者 CPU 的負荷一直很高,就是一種警訊。使用像是 Query Monitor 這類的開發者工具,會將每一條 SQL 語句都列出來,當發現更新的對象為資料表
wp_options的rewrite_rules欄位,就必須清查網站上的外掛,搜尋flush_rewrite_rules關鍵字。